#include <llvm/Pass.h>
#include <llvm/PassManager.h>
#include <llvm/ADT/SmallVector.h>
#include <llvm/IR/Verifier.h>
#include <llvm/IR/BasicBlock.h>
#include <llvm/IR/CallingConv.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/GlobalVariable.h>
#include <llvm/IR/IRPrintingPasses.h>
#include <llvm/IR/InlineAsm.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/Support/FormattedStream.h>
#include <llvm/Support/MathExtras.h>

#include <algorithm>
#include <iostream>

#include <list>

#include "util-define.h"
#include "sintatico-arvore.h"
#include "llvm.h"

using namespace llvm;
using namespace std;

Module* makeLLVMModule(no *root);
map<string,AllocaInst *> geraDeclaracoes(BasicBlock* funcaoMain_block, no *noDeclaracoes);
void geraCorpo(BasicBlock* funcaoMain_block, no *noCorpo, map<string,AllocaInst *> mapVariaveis);
int geraCodigoExpressao(no *noExpressao);
void listaVariaveis(no* noVariavel, list<pair<string, string>> &lista);
BinaryOperator* geraOperacaoBinaria(BasicBlock* funcaoMain_block, no *noOperacao, map<string,AllocaInst *> mapVariaveis);

int geraCodigoLLVM(no *root){  
  if(root->children[0]->tipoNoNaoTeminal != NTLETEXP){
    std::cout<<"Implementado somente para um programa simples com let in end nao aninhado.\n";
    return 0;
  }
  
  return geraCodigoExpressao(root->children[0]);
}

int geraCodigoExpressao(no *noExpressao){        
  Module* Mod = makeLLVMModule(noExpressao);
  verifyModule(*Mod,&outs());
  PassManager PM;
  PM.add(createPrintModulePass(outs()));
  PM.run(*Mod);  
  return 0;    
}

map<string,AllocaInst *> geraDeclaracoes(BasicBlock* funcaoMain_block, no *noDeclaracoes){
  list<pair<string, string>> lista;
  listaVariaveis(noDeclaracoes,lista);   
  map<string,AllocaInst *> mapVariaveis;
  
  for(auto pair:lista){
    ConstantInt* constVarInteiro = ConstantInt::get(funcaoMain_block->getContext(), APInt(32, StringRef(pair.second), 10));
    AllocaInst* nomeVar = new AllocaInst(IntegerType::get(funcaoMain_block->getContext(), 32), pair.first, funcaoMain_block);    
    mapVariaveis[pair.first] = nomeVar;    
    StoreInst* storeIntrucao = new StoreInst(constVarInteiro, nomeVar, false, funcaoMain_block);
  }
  
  return mapVariaveis;  
}

pair<string, string> geraParVarValor(no* noVariavel){
  return make_pair(noVariavel->children[0]->token->value, noVariavel->children[2]->token->value);
}

void listaVariaveis(no* noVariavel, list<pair<string, string>> &lista){
  if(noVariavel->tamChildren == 0){
    return;
  }
  
  lista.push_back(geraParVarValor(noVariavel->children[0]));  
  listaVariaveis(noVariavel->children[1], lista);    
}


void geraCorpo(BasicBlock* funcaoMain_block, no *noCorpo, map<string,AllocaInst *> mapVariaveis){
  BinaryOperator* resultadoOp = geraOperacaoBinaria(funcaoMain_block,noCorpo->children[0]->children[1], mapVariaveis);
  
  AllocaInst* varResultado = mapVariaveis[noCorpo->children[0]->children[0]->children[0]->token->value];  
  StoreInst* resultadoOperacao = new StoreInst(resultadoOp, varResultado, false, funcaoMain_block);
}

BinaryOperator* geraOperacaoBinaria(BasicBlock* funcaoMain_block, no *noOperacao, map<string,AllocaInst *> mapVariaveis){
  AllocaInst* operador1 = mapVariaveis[noOperacao->children[0]->children[0]->children[0]->token->value];
  AllocaInst* operador2 = mapVariaveis[noOperacao->children[2]->children[0]->children[0]->token->value];
  string operando = noOperacao->children[1]->token->value;
  BinaryOperator* resultado;
  
  if (operando == "+"){
    LoadInst* loadOperando1 = new LoadInst(operador1, "", false, funcaoMain_block);
    LoadInst* loadOperando2 = new LoadInst(operador2, "", false, funcaoMain_block);    
    return resultado = BinaryOperator::Create(Instruction::Add, loadOperando1, loadOperando2, "", funcaoMain_block);
  }
    
}
 
Module* makeLLVMModule(no *noExpressao) {
  Module* mod = new Module("soma.ll", getGlobalContext());
   
  std::vector<Type*>funcaoMain_args;
  FunctionType *funcaoMain_type = FunctionType::get(
    /*Result=*/Type::getVoidTy(mod->getContext()),
    /*Params=*/funcaoMain_args,
    /*isVarArg=*/false);
 
  Function* funcaoMain = mod->getFunction("main"); 
  funcaoMain = Function::Create(
    /*Type=*/funcaoMain_type,
    /*Linkage=*/GlobalValue::ExternalLinkage,
    /*Name=*/"main", mod);

     
  BasicBlock* funcaoMain_block = BasicBlock::Create(mod->getContext(),"Principal",funcaoMain,0);
   
  map<string,AllocaInst *> mapVariaveis = geraDeclaracoes(funcaoMain_block, noExpressao->children[0]);
  geraCorpo(funcaoMain_block, noExpressao->children[1], mapVariaveis);
 
  ReturnInst::Create(mod->getContext(), funcaoMain_block);
  return mod;
}
